﻿using System;
using System.Collections.Generic;
using UnityEngine;
using Utility.PoolSystem;

namespace Utility.DataSystem
{
	public interface IDataFieldContextOwner
	{
		IDataFieldContext Context { get; }
	}

	public interface IDataFieldContext
	{
		string Name { get; }
		DataValue QueryField (FieldQuery fieldQuery);
		DataValue QueryField_Internal (FieldQuery fieldQuery);
		void OnEvent (FieldEvent fieldEvent);
		void OnEvent_Internal (FieldEvent fieldEvent);
		void OnAction (FieldAction fieldAction);
		void OnAction_Internal (FieldAction fieldAction);
	}

	public interface ISubContextsOwner
	{
		void GetSubContexts (FieldContexts contexts);
	}

	public abstract class DataFieldContext : IDataFieldContext, ISubContextsOwner, IFieldSource, IFieldEventListener, IFieldActionPerformer
	{
		public virtual int ContextFlag => 0;
		public virtual bool EnableValueCaching => true;

		public virtual string Name => GetType ().Name;

		public void Clear ()
		{
			ClearCache_FieldSources ();
			ClearCache_EventListener ();
			ClearCache_ActionPerformers ();

			ClearCache_Values ();
		}


		#region context
		static Stack<IDataFieldContext> contextStack = new Stack<IDataFieldContext> ();
		protected virtual void GetSubContexts (FieldContexts contexts) { }
		void ISubContextsOwner.GetSubContexts (FieldContexts contexts) => GetSubContexts (contexts);

		protected virtual void CollectContexts (int contextFlag, FieldContexts contexts)
		{
			contexts.Add (this);
			contexts.Add (contextStack);
		}

		protected virtual void CollectContexts (FieldQuery query, FieldContexts contexts)
		{
			CollectContexts (0, contexts);
		}
		protected virtual void CollectContexts (FieldEvent e, FieldContexts contexts)
		{
			CollectContexts (0, contexts);
		}
		protected virtual void CollectContexts (FieldAction a, FieldContexts contexts)
		{
			CollectContexts (0, contexts);
		}
		#endregion
		#region field
		#region internal
		protected Dictionary<DataFieldSetting, FieldSources> cache_fieldSources = new Dictionary<DataFieldSetting, FieldSources> ();

		protected virtual void CollectFieldSources (FieldSources sources)
		{
			sources.Add (this);
		}

		public void ClearCache_FieldSources ()
		{
			foreach (var sources in cache_fieldSources.Values)
			{
				sources?.Dispose ();
			}
			cache_fieldSources.Clear ();
		}

		DataValue IDataFieldContext.QueryField_Internal (FieldQuery fieldQuery)
		{
			if (fieldQuery.Setting.IsContextLocal && fieldQuery.SourceContext != this)
			{
				return default;
			}

			var msg = FieldCenter.GetDebugMsg (fieldQuery);
			contextStack.Push (fieldQuery.SourceContext);
			//try get from cache
			if (!TryGetCachedValue (fieldQuery, true, out var value))
			{
				//try get cached sources
				if (cache_fieldSources.TryGetValue (fieldQuery.Setting, out var sources))
				{
					value = sources?.GetData (this, fieldQuery) ?? default;
				}
				else
				{
					sources = FieldSources.New ();
					CollectFieldSources (sources);
					//get value and remove the not interested sources
					value = sources.GetDataAndFilter (this, fieldQuery);
					if (sources.IsEmpty)
					{
						sources.Dispose ();
						sources = null;
					}
					//cache sources
					cache_fieldSources[fieldQuery.Setting] = sources;
				}

				//cache value
				CacheFieldValue (fieldQuery.Setting, true, value);
			}
			else
			{
				msg?.FromCache ();
			}
			contextStack.Pop ();
			return value;
		}
		#endregion
		#region general
		DataValue IDataFieldContext.QueryField (FieldQuery fieldQuery)
		{
			var msg = FieldCenter.GetDebugMsg (fieldQuery);

			msg?.StartQuery (fieldQuery, this);
			//try get from cache
			if (!TryGetCachedValue (fieldQuery, false, out var value))
			{
				if (fieldQuery.Setting.IsResult)
				{
					//from composition
					value = fieldQuery.Setting.GetComposedData (this, fieldQuery);
				}
				else
				{
					//init
					value = fieldQuery.Setting.InitValue;
					msg?.InitValue (value);
					//from contexts
					using (var contexts = FieldContexts.New ())
					{
						//CollectContexts (fieldQuery.TargetEntities, contexts);
						CollectContexts (fieldQuery, contexts);
						value = fieldQuery.Setting.CombineValue (value, contexts.GetData (fieldQuery));
					}
				}
				//validate
				value = fieldQuery.Setting.ValidateValue (value);
				//cache
				CacheFieldValue (fieldQuery.Setting, false, value);
			}
			else
			{
				msg?.FromCache ();
			}
			msg?.EndQuery (fieldQuery, value);
			return value;
		}
		#endregion
		#region field source
		protected virtual DataValue GetData (FieldQuery fieldQuery, out bool isInterested)
		{
			isInterested = false;
			return default;
		}
		DataValue IFieldSource.GetData (IDataFieldContext context, FieldQuery fieldQuery, out bool isInterested) => GetData (fieldQuery, out isInterested);
		#endregion
		#endregion
		#region event
		#region internal
		protected Dictionary<FieldEventSetting, FieldEventListeners> cache_eventListeners = new Dictionary<FieldEventSetting, FieldEventListeners> ();

		public void ClearCache_EventListener ()
		{
			foreach (var listener in cache_eventListeners.Values)
			{
				listener?.Dispose ();
			}
			cache_eventListeners.Clear ();
		}
		protected virtual void CollectEventListeners (FieldEventListeners listeners)
		{
			listeners.Add (this);
		}

		void IDataFieldContext.OnEvent_Internal (FieldEvent fieldEvent)
		{
			contextStack.Push (fieldEvent.SourceContext);
			if (cache_eventListeners.TryGetValue (fieldEvent.Setting, out var listeners))
			{
				listeners?.OnEvent (this, fieldEvent);
			}
			else
			{
				listeners = FieldEventListeners.New ();
				CollectEventListeners (listeners);
				//invoke event and filter
				listeners.OnEventAndFilter (this, fieldEvent);
				if (listeners.IsEmpty)
				{
					listeners.Dispose ();
					listeners = null;
				}
				//cache
				cache_eventListeners[fieldEvent.Setting] = listeners;
			}
			contextStack.Pop ();
		}
		#endregion
		#region general
		void IDataFieldContext.OnEvent (FieldEvent fieldEvent)
		{
			var msg = FieldCenter.GetDebugMsg (fieldEvent);

			msg?.StartEvent (fieldEvent, this);
			using (var contexts = FieldContexts.New ())
			{
				//CollectContexts (fieldEvent.TargetEntities, contexts);
				CollectContexts (fieldEvent, contexts);
				contexts.OnEvent (fieldEvent);
			}
			msg?.EndEvent (fieldEvent, this);
		}
		#endregion
		#region event listener
		protected virtual void OnEvent (FieldEvent e, out bool isInterested) => isInterested = false;
		void IFieldEventListener.OnEvent (IDataFieldContext context, FieldEvent e, out bool isInterested) => OnEvent (e, out isInterested);
		#endregion
		#endregion
		#region action
		#region internal
		protected Dictionary<FieldActionSetting, FieldActionPerformers> cache_actionPerformers = new Dictionary<FieldActionSetting, FieldActionPerformers> ();
		protected virtual void CollectActionPerformers (FieldActionPerformers performers)
		{
			performers.Add (this);
		}

		public void ClearCache_ActionPerformers ()
		{
			foreach (var performer in cache_actionPerformers.Values)
			{
				performer?.Dispose ();
			}
			cache_actionPerformers.Clear ();
		}

		void IDataFieldContext.OnAction_Internal (FieldAction fieldAction)
		{
			if (fieldAction.Setting.IsContextLocal && fieldAction.SourceContext != this)
			{
				return;
			}

			contextStack.Push (fieldAction.SourceContext);
			if (cache_actionPerformers.TryGetValue (fieldAction.Setting, out var performers))
			{
				performers?.PerformAction (this, fieldAction);
			}
			else
			{
				performers = FieldActionPerformers.New ();
				CollectActionPerformers (performers);
				//perform action and filter
				performers.PerformActionAndFilter (this, fieldAction);

				if (performers.IsEmpty)
				{
					performers.Dispose ();
					performers = null;
				}
				//cache
				cache_actionPerformers[fieldAction.Setting] = performers;
			}
			contextStack.Pop ();
		}
		#endregion
		#region general
		void IDataFieldContext.OnAction (FieldAction fieldAction)
		{
			var msg = FieldCenter.GetDebugMsg (fieldAction);

			msg?.StartAction (fieldAction, this);
			using (var contexts = FieldContexts.New ())
			{
				//CollectContexts (fieldAction.TargetEntities, contexts);
				CollectContexts (fieldAction, contexts);
				contexts.OnAction (fieldAction);
			}
			msg?.EndAction (fieldAction, this);
		}
		#endregion
		#region action performer
		protected virtual void PerformAction (FieldAction fieldAction, out bool isInterested) => isInterested = false;
		void IFieldActionPerformer.PerformAction (IDataFieldContext context, FieldAction fieldAction, out bool isInterested) => PerformAction (fieldAction, out isInterested);
		#endregion
		#endregion

		#region data cache
		Dictionary<DataFieldSetting, DataValue> cache_values = new Dictionary<DataFieldSetting, DataValue> ();
		Dictionary<DataFieldSetting, DataValue> cache_values_local = new Dictionary<DataFieldSetting, DataValue> ();

		public void ClearCache_Values ()
		{
			cache_values.Clear ();
			cache_values_local.Clear ();
		}

		void CacheFieldValue (DataFieldSetting field, bool local, DataValue value)
		{
			if (EnableValueCaching)
			{
				(local ? cache_values_local : cache_values)[field] = value;
			}
		}

		bool TryGetCachedValue (FieldQuery query, bool local, out DataValue value)
		{
			if (EnableValueCaching && !query.HasParam && query.Setting.AllowCache && !query.RefreshValue)
			{
				return (local ? cache_values_local : cache_values).TryGetValue (query.Setting, out value);
			}
			else
			{
				value = default;
				return false;
			}
		}
		#endregion
	}

	public static class DataFieldContextEX
	{
		#region field
		public static DataValue GetValue (this IDataFieldContext context, DataFieldSetting field, bool refreshValue = false, IList<DataValue> parameters = null)
		{
			return field?.QueryValue (context, refreshValue, parameters) ?? default;
		}

		public static DataValue GetValue (this IDataFieldContext context, string key, bool refreshValue = false, IList<DataValue> parameters = null)
		{
			DataFieldSetting field = FieldCenter.GetField (key);
			if (field != null)
			{
				return context.GetValue (field, refreshValue, parameters);
			}
			else
			{
				throw new Exception ($"Cannot find field: {key}");
			}
		}

		public static DataValue GetValue (this IDataFieldContext context, string resultKey, FieldComponents componentType, bool refreshValue = false, IList<DataValue> parameters = null)
		{
			return context.GetValue (componentType.GetKey (resultKey), refreshValue, parameters);
		}
		#endregion
		#region event
		public static void InvokeEvent (this IDataFieldContext context, FieldEventSetting eventSetting)
		{
			eventSetting.InvokeEvent (context);
		}

		public static void InvokeEvent (this IDataFieldContext context, string fieldEvent)
		{
			var setting = FieldCenter.GetEvent (fieldEvent);
			if (setting != null)
			{
				context.InvokeEvent (setting);
			}
			else
			{
				throw new Exception ($"Cannot find event: {fieldEvent}");
			}
		}
		#endregion
		#region action
		public static void PerformAction (this IDataFieldContext context, FieldActionSetting actionSetting, IList<DataValue> parameters)
		{
			actionSetting.PerformAction (context, parameters);
		}

		public static void PerformAction (this IDataFieldContext context, string action, IList<DataValue> parameters)
		{
			var fieldAction = FieldCenter.GetAction (action);
			if (fieldAction != null)
			{
				context.PerformAction (fieldAction, parameters);
			}
			else
			{
				throw new Exception ($"Cannot find action: {action}");
			}
		}
		#endregion
	}

	public class FieldContexts : IDisposable, IPoolCallback
	{
		static SimplePool<FieldContexts> Pool { get; } = new SimplePool<FieldContexts> (() => new FieldContexts ());

		public static FieldContexts New () => Pool.Spawn ();

		public void Dispose ()
		{
			Pool.Despawn (this);
		}

		void IPoolCallback.OnSpawn ()
		{
			set.Clear ();
		}

		void IPoolCallback.OnDespawn ()
		{
			set.Clear ();
		}
		FieldContexts () { }
		HashSet<IDataFieldContext> set = new HashSet<IDataFieldContext> ();


		public void Add (IDataFieldContext context, bool includeSubContexts = true)
		{
			if (set.Add (context) && includeSubContexts)
			{
				(context as ISubContextsOwner)?.GetSubContexts (this);
			}
		}

		public void Add (IEnumerable<IDataFieldContext> contexts, bool includeSubContexts = true)
		{
			using (var tempList = BufferList<IDataFieldContext>.Spawn(contexts))
			{
				foreach (var context in tempList)
				{
					Add (context, includeSubContexts);
				}
			}
		}

		public void Add (IDataFieldContextOwner owner, bool includeSubContexts = true)
		{
			Add (owner?.Context, includeSubContexts);
		}

		public void Add (IEnumerable<IDataFieldContextOwner> owners, bool includeSubContexts = true)
		{
			if (owners == null)
				return;
			foreach (var owner in owners)
			{
				Add (owner, includeSubContexts);
			}
		}

		public void Remove (Predicate<IDataFieldContext> condition)
		{
			set.RemoveWhere (condition);
		}

		public DataValue GetData (FieldQuery fieldQuery)
		{
			var msg = FieldCenter.GetDebugMsg (fieldQuery);

			DataValue data = default;
			foreach (var context in set)
			{
				if (context != null)
				{
					msg?.StartContext (context);
					var value = context.QueryField_Internal (fieldQuery);
					data = fieldQuery.Setting.CombineValue (data, value);
					msg?.EndContext (value);
				}
			}

			return data;
		}

		public void OnEvent (FieldEvent fieldEvent)
		{
			var msg = FieldCenter.GetDebugMsg (fieldEvent);

			foreach (var context in set)
			{
				if (context != null)
				{
					msg?.StartContext (context);
					context.OnEvent_Internal (fieldEvent);
					msg?.EndContext ();
				}
			}
		}

		public void OnAction (FieldAction fieldAction)
		{
			var msg = FieldCenter.GetDebugMsg (fieldAction);

			foreach (var context in set)
			{
				if (context != null)
				{
					msg?.StartContext (context);
					context.OnAction_Internal (fieldAction);
					msg?.EndContext ();
				}
			}
		}
	}
}
